package com.star.common.oss.service.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.CreateBucketRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectResult;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.star.common.oss.exception.OssException;
import com.star.common.oss.model.FileUploadStatusVO;
import com.star.common.oss.model.OssStsVO;
import com.star.common.oss.model.OssTemporaryUrlVO;
import com.star.common.oss.properties.OssProperties;
import com.star.common.oss.service.AbstractOssClient;
import com.star.common.oss.utils.StrPoolUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Objects;

/**
 * 阿里云oss处理类
 *
 * @author zhouhengzhe #公众号：小白的Java进阶之路
 * @version 1.0
 * @date 2023/6/27 14:09
 */
@Slf4j
public class AliyunOssClient extends AbstractOssClient {

    private final OSS ossClient;


    private final OssProperties.AliyunOssProperties aliyunOssProperties;

    private static final long DURATION_SECOND = 3600L;

    public AliyunOssClient(OssProperties.AliyunOssProperties aliyunOssProperties) {
        super.checkAllPropertiesNotNull(aliyunOssProperties);
        this.ossClient = new OSSClientBuilder()
                .build(aliyunOssProperties.getEndpoint(),
                        aliyunOssProperties.getAccessKeyId(),
                        aliyunOssProperties.getAccessKeySecret());
        this.aliyunOssProperties = aliyunOssProperties;
    }


    /**
     * 获取临时访问凭证
     *
     * @param sessionName   SessionName的长度和规则，不要有空格，只能有'-' '_' 字母和数字等字符
     * @param bucketName    oss桶 例如 ldstore
     * @param regionId      RAM的地域ID。以华东1（杭州）地域为例，regionID填写为cn-hangzhou。也可以保留默认值，默认值为空字符串（""）。
     * @param stsApiVersion sts版本
     * @param roleArn       获取的角色ARN
     * @param policyJson    比如以下Policy用于限制仅允许使用临时访问凭证向目标存储空间examplebucket下的src目录上传文件。
     *                      临时访问凭证最后获得的权限是步骤4设置的角色权限和该Policy设置权限的交集，即仅允许将文件上传至目标存储空间examplebucket下的src目录。
     *                      如果policy为空，则用户将获得该角色下所有权限。
     *                      {\n" +
     *                      "    \"Version\": \"1\", \n" +
     *                      "    \"Statement\": [\n" +
     *                      "        {\n" +
     *                      "            \"Action\": [\n" +
     *                      "                \"oss:PutObject\"\n" +
     *                      "            ], \n" +
     *                      "            \"Resource\": [\n" +
     *                      "                \"acs:oss:*:*:examplebucket/src/*\" \n" +
     *                      "            ], \n" +
     *                      "            \"Effect\": \"Allow\"\n" +
     *                      "        }\n" +
     *                      "    ]\n" +
     *                      "}
     * @return 临时访问凭证
     */
    @Override
    public OssStsVO getSts(String sessionName,
                           String bucketName,
                           String regionId,
                           String stsApiVersion,
                           String roleArn,
                           String policyJson) {
        // RoleSessionName 是临时Token的会话名称，自己指定用于标识你的用户，主要用于审计，或者用于区分Token颁发给谁
        super.checkProperties(sessionName, bucketName);
        try {
            // 此处必须为 HTTPS
            ProtocolType protocolType = ProtocolType.HTTPS;
            String policy = String.format(policyJson, bucketName, bucketName);
            // 创建一个 Aliyun Acs Client, 用于发起 OpenAPI 请求
            IClientProfile profile = DefaultProfile.getProfile(regionId, aliyunOssProperties.getAccessKeyId(),
                    aliyunOssProperties.getAccessKeySecret());
            DefaultAcsClient client = new DefaultAcsClient(profile);
            // 创建一个 AssumeRoleRequest 并设置请求参数
            final AssumeRoleRequest request = new AssumeRoleRequest();
            request.setVersion(stsApiVersion);
            request.setSysMethod(MethodType.POST);
            request.setSysProtocol(protocolType);
            request.setRoleArn(roleArn);
            request.setRoleSessionName(sessionName);
            request.setPolicy(policy);
            request.setDurationSeconds(DURATION_SECOND);
            // 发起请求，并得到response
            AssumeRoleResponse acsResponse = client.getAcsResponse(request);
            if (Objects.isNull(acsResponse)) {
                throw new OssException("the acs response is null");
            }
            return OssStsVO
                    .builder()
                    .accessKeyId(acsResponse.getCredentials().getAccessKeyId())
                    .accessKeySecret(acsResponse.getCredentials().getAccessKeySecret())
                    .securityToken(acsResponse.getCredentials().getSecurityToken())
                    .expiration(acsResponse.getCredentials().getExpiration())
                    .build();
        } catch (Exception e) {
            throw new OssException("Obtain temporary access credentials fail by aliyun ！", e);
        }

    }

    /**
     * 获取临时url
     *
     * @param contentType  请求头
     * @param bucketName   桶名
     * @param objectKey    文件名
     * @param documentType 文件类型 例如 jpg
     * @return
     */
    @Override
    public OssTemporaryUrlVO getTemporaryUrl(String contentType,
                                             String bucketName,
                                             String objectKey,
                                             String documentType) {
        throw new OssException("Features are not under development get temporary url by aliyun");
    }

    /**
     * 文件上传
     *
     * @param uploadFile 上传的文件
     * @param fileName   文件名称
     * @param bucketName 桶名称
     * @return
     */
    @Override
    public String uploadFile(MultipartFile uploadFile,
                             String fileName,
                             String bucketName) {
        try {
            createBucket(bucketName);
            PutObjectResult putObjectResult = ossClient.putObject(fileName, bucketName, uploadFile.getInputStream());
            //检查返回值结果
            checkPutObject(putObjectResult);
            return StrPoolUtils.HTTP +
                    bucketName +
                    StrPoolUtils.DOT +
                    aliyunOssProperties.getEndpoint() +
                    StrPoolUtils.SLASH +
                    fileName;
        } catch (Exception e) {
            throw new OssException("upload file fail", e);
        }
    }

    /**
     * 不存在就创建桶
     *
     * @param bucketName 桶名称
     */
    private void createBucket(String bucketName) {
        //不存在桶
        if (!checkBucket(bucketName)) {
            //创建一个桶,设置仓库名称
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            //设置仓库权限公共读
            createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
            ossClient.createBucket(createBucketRequest);
        }
    }

    /**
     * 判断是否存在桶
     *
     * @param bucketName 桶名称
     * @return 返回是否存在
     */
    public boolean checkBucket(String bucketName) {
        return ossClient.doesBucketExist(bucketName);
    }

    /**
     * 检查返回值结果
     *
     * @param putObjectResult 返回值结果
     */
    private static void checkPutObject(PutObjectResult putObjectResult) {
        if (Objects.isNull(putObjectResult) ||
                Objects.isNull(putObjectResult.getResponse()) ||
                putObjectResult.getResponse().getStatusCode() != 200) {
            throw new OssException("The return value for uploaded files is null");
        }
    }

    /**
     * 文件流上传
     *
     * @param fileName    文件名称
     * @param bucketName  桶名称
     * @param out         文件流
     * @param contentType 文件类型
     * @return
     */
    @Override
    public String uploadFileByte(String fileName,
                                 String bucketName,
                                 byte[] out,
                                 String contentType) {
        try {
            createBucket(bucketName);
            //检查返回值结果
            PutObjectResult putObjectResult = ossClient.putObject(bucketName, fileName, new ByteArrayInputStream(out));
            checkPutObject(putObjectResult);
            return StrPoolUtils.HTTP +
                    bucketName +
                    StrPoolUtils.DOT +
                    aliyunOssProperties.getEndpoint() +
                    StrPoolUtils.SLASH +
                    fileName;
        } catch (Exception e) {
            throw new OssException("upload file fail", e);
        }
    }

    /**
     * 获取文件上传进度(<a href="https://help.aliyun.com/document_detail/84796.htm?spm=a2c4g.408162.0.0.3dfdad8bHsy8lg">...</a>)
     *
     * @param fileName   文件名称
     * @param bucketName 桶名称
     * @return 返回文件上传进度
     */
    @Override
    public FileUploadStatusVO getFileUploadPlan(String fileName,
                                                String bucketName) {
        throw new OssException("Features are not under development get file upload plan by aliyun");
    }

    /**
     * 文件下载
     *
     * @param response   响应
     * @param fileName   文件名称
     * @param bucketName 桶名称
     */
    @Override
    public void fileDownload(HttpServletResponse response,
                             String fileName,
                             String bucketName) {
        try {
            OSSObject ossObject = ossClient.getObject(bucketName, fileName);
            InputStream in = ossObject.getObjectContent();
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
            IOUtils.copy(in, response.getOutputStream());
        } catch (Exception e) {
            throw new OssException("download file fail", e);
        }
    }

    /**
     * 删除文件
     *
     * @param fileName   文件名称
     * @param bucketName 桶名称
     */
    @Override
    public void deleteFile(String fileName,
                           String bucketName) {
        ossClient.deleteObject(bucketName, fileName);
    }
}
